'
' *  Authors:  Benton Stark
' * 
' *  Copyright (c) 2007-2012 Starksoft, LLC (http://www.starksoft.com) 
' *
' * Permission is hereby granted, free of charge, to any person obtaining a copy
' * of this software and associated documentation files (the "Software"), to deal
' * in the Software without restriction, including without limitation the rights
' * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
' * copies of the Software, and to permit persons to whom the Software is
' * furnished to do so, subject to the following conditions:
' * 
' * The above copyright notice and this permission notice shall be included in
' * all copies or substantial portions of the Software.
' * 
' * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
' * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
' * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
' * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
' * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
' * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
' * THE SOFTWARE.
' * 
' 


Imports System.Collections
Imports System.Collections.Generic
Imports System.Text


Namespace FTP
    ''' <summary>
    ''' Ftp response collection.
    ''' </summary>
    Public Class FtpFeatureCollection
        Implements IEnumerable(Of FtpFeature)
        Private _list As New List(Of FtpFeature)()
        Private _text As String

        Private Const FEAT_PREAMBLE_LEN As Integer = 4
        Private Const FEAT_EPILOGUE_LEN As Integer = 7
        Private Const FEAT_PREAMBLE As String = "211-"
        Private Const NO_FEAT_PREAMBLE As String = "211 "
        Private Const FEAT_EPILOGUE As String = "211 END"


        ''' <summary>
        ''' Default constructor for no features.
        ''' </summary>
        Public Sub New()
            _text = NO_FEAT_PREAMBLE
        End Sub

        ''' <summary>
        ''' Default constructor with features.
        ''' </summary>
        ''' <param name="text">Raw feature list text.</param>
        Public Sub New(text As String)
            If [String].IsNullOrEmpty(text) Then
                Throw New FtpFeatureException("text")
            End If

            If text.Length < FEAT_PREAMBLE_LEN Then
                Throw New FtpFeatureException("text argument length too short")
            End If

            _text = text
            Parse()
        End Sub

        ''' <summary>
        ''' Searches for the specified object and return the zero-based index of the
        ''' first occurrence within the entire FtpFeatureCollection list.
        ''' </summary>
        ''' <param name="item">The FtpFeature object to locate in the collection.</param>
        ''' <return>The zero-based index of the first occurrence of item within the entire if found; otherwise, -1.</return>
        Public Function IndexOf(item As FtpFeature) As Integer
            Return _list.IndexOf(item)
        End Function

        ''' <summary>
        ''' Adds an FtpFeature to the end of the FtpFeatureCollection list.
        ''' </summary>
        ''' <param name="item">FtpFeature object to add.</param>
        Public Sub Add(item As FtpFeature)
            _list.Add(item)
        End Sub

        ''' <summary>
        '''  Gets the number of elements actually contained in the FtpFeatureCollection list.
        ''' </summary>
        Public ReadOnly Property Count() As Integer
            Get
                Return _list.Count
            End Get
        End Property

        Public Function GetEnumerator() As IEnumerator(Of FtpFeature) Implements IEnumerable(Of FtpFeature).GetEnumerator
            Return _list.GetEnumerator()
        End Function

        ''' <summary>
        ''' Gets an FtpFeature from the FtpFeatureCollection list based on index value.
        ''' </summary>
        ''' <param name="index">Numeric index of item to retrieve.</param>
        ''' <return>FtpFeature object.</return>
        Default Public ReadOnly Property Item(index As Integer) As FtpFeature
            Get
                Return _list(index)
            End Get
        End Property

        ''' <summary>
        ''' Gets an FtpFeature from the FtpFeatureCollection list based on name.
        ''' </summary>
        ''' <param name="name">Name of the feature.</param>
        ''' <return>FtpFeature object.</return>
        Default Public ReadOnly Property Item(name As String) As FtpFeature
            Get
                Return Find(name)
            End Get
        End Property

        ''' <summary>
        ''' Gets an FtpFeature from the FtpFeatureCollection list based on name.
        ''' </summary>
        ''' <param name="name">Name of the feature.</param>
        ''' <return>FtpFeature object.</return>
        Default Public ReadOnly Property Item(name As FtpCmd) As FtpFeature
            Get
                Return Find(name)
            End Get
        End Property


        ''' <summary>
        ''' Remove all elements from the FtpFeatureCollection list.
        ''' </summary>
        Public Sub Clear()
            _list.Clear()
        End Sub

        ''' <summary>
        ''' Get the raw FTP server supplied reponse text for features.
        ''' </summary>
        ''' <return>A string containing the FTP feature list.</return>
        Public Function GetRawText() As String
            Return _text
        End Function

        ''' <summary>
        ''' Linearly searches for the specified object based on the feature 'name' parameter value
        ''' and return the corresponding object with the name is found; otherwise null.  Search is case insensitive.
        ''' </summary>
        ''' <remarks>
        ''' example:  col.Find("UTF8");
        ''' </remarks>
        ''' <param name="name">The name of the FtpFeature to locate in the collection.</param>
        ''' <return>FtpFeature object if the name if found; otherwise null.</return>
        Public Function Find(name As String) As FtpFeature
            If [String].IsNullOrEmpty(name) Then
                Throw New ArgumentNullException("name", "must have a value")
            End If

            For Each item As FtpFeature In _list
                If [String].Compare(name, item.Name, True) = 0 Then
                    Return item
                End If
            Next

            Return Nothing
        End Function

        ''' <summary>
        ''' Linearly searches for the specified object based on the feature 'name' parameter value
        ''' and return the corresponding object with the name is found; otherwise null.  Search is case insensitive.
        ''' </summary>
        ''' <param name="name">The name of the FtpFeature to locate in the collection.</param>
        ''' <return>FtpFeature object if the name if found; otherwise null.</return>
        Public Function Find(name As FtpCmd) As FtpFeature
            Return Find(name.ToString())
        End Function

        ''' <summary>
        ''' Linearly searches for the specified object based on the feature 'name' parameter value
        ''' and return true if an object with the name is found; otherwise false.  Search is case insensitive.
        ''' </summary>
        ''' <remarks>
        ''' example:  col.Contains("UTF8");
        ''' </remarks>
        ''' <param name="name">The name of the FtpFeature to locate in the collection.</param>
        ''' <return>True if the name if found; otherwise false.</return>
        Public Function Contains(name As String) As Boolean
            Return If(Find(name) IsNot Nothing, True, False)
        End Function

        ''' <summary>
        ''' Linearly searches for the specified object based on the feature 'name' and 'argument' parameter values
        ''' and return true if an object with the name is found; otherwise false.  Search is case insensitive.
        ''' </summary>
        ''' <remarks>
        ''' examples:  col.Contains("REST", "STREAM");
        '''            col.Contains(FtpCmd.Hash, "SHA-1");
        ''' </remarks>
        ''' <param name="name">The name of the FtpFeature to locate in the collection.</param>
        ''' <param name="argument">The argument for the FtpFeature to locate in the collection.</param>
        ''' <return>True if the name if found; otherwise false.</return>
        Public Function Contains(name As String, argument As String) As Boolean
            If [String].IsNullOrEmpty(name) Then
                Throw New ArgumentNullException("name", "must have a value")
            End If

            If [String].IsNullOrEmpty(argument) Then
                Throw New ArgumentNullException("argument", "must have a value")
            End If

            Dim f As FtpFeature = Find(name)

            If f Is Nothing Then
                Return False
            Else
                For Each arg As FtpFeatureArgument In f.Arguments
                    If [String].Compare(argument, arg.Name, True) = 0 Then
                        Return True
                    End If
                Next
            End If
            Return False
        End Function

        ''' <summary>
        ''' Linearly searches for the specified object based on the feature 'name' parameter value
        ''' and return true if an object with the name is found; otherwise false.  Search is case insensitive.
        ''' </summary>
        ''' <param name="name">The name of the FtpFeature to locate in the collection.</param>
        ''' <return>True if the name if found; otherwise false.</return>
        Public Function Contains(name As FtpCmd) As Boolean
            Return Contains(name.ToString())
        End Function

        ''' <summary>
        ''' Linearly searches for the specified object based on the feature 'name' parameter value
        ''' and return true if an object with the name is found; otherwise false.  Search is case insensitive.
        ''' </summary>
        ''' <param name="name">The name of the FtpFeature to locate in the collection.</param>
        ''' <param name="arguments">The argument for the FtpFeature to locate in the collection.</param>
        ''' <return>True if the name if found; otherwise false.</return>
        Public Function Contains(name As FtpCmd, arguments As String) As Boolean
            Return Contains(name.ToString(), arguments)
        End Function

        ''' <summary>
        ''' Parses the raw features text into objects and loads them into the collection.
        ''' </summary>
        ''' <remarks>
        ''' http://tools.ietf.org/html/rfc2389#section-3
        ''' 
        ''' Note that each feature line in the feature-listing begins with a
        ''' single space.  That space is not optional, nor does it indicate
        ''' general white space.  This space guarantees that the feature line can
        ''' never be misinterpreted as the end of the feature-listing, but is
        ''' required even where there is no possibility of ambiguity.
        ''' 
        ''' Syntax:
        ''' feat-response   = error-response / no-features / feature-listing
        '''         no-features     = "211" SP *TCHAR CRLF
        '''         feature-listing = "211-" *TCHAR CRLF
        '''                           1*( SP feature CRLF )
        '''                           "211 End" CRLF
        '''         feature         = feature-label [ SP feature-parms ]
        '''         feature-label   = 1*VCHAR
        '''         feature-parms   = 1*TCHAR
        '''         
        ''' Example:
        '''         C> FEAT
        '''         S> 211-Extensions supported:
        '''         S>  MLST size*;create;modify*;perm;media-type
        '''         S>  SIZE
        '''         S>  COMPRESSION
        '''         S>  MDTM
        '''         S> 211 END
        ''' 
        ''' </remarks>
        Private Sub Parse()
            If _text.Length < FEAT_PREAMBLE_LEN Then
                Throw New FtpFeatureException("preamble length not valid")
            End If

            Dim preamble As String = _text.Substring(0, FEAT_PREAMBLE_LEN).ToUpper()
            ' test to see if there are no features listed
            If preamble = NO_FEAT_PREAMBLE Then
                Return
                ' test to make sure preamble is correct
            ElseIf preamble <> FEAT_PREAMBLE Then
                Throw New FtpFeatureException("preamble not found while parsing feature list")
            End If

            Dim lines As String() = SplitCrLf(_text)

            ' spin the lines of the features
            Dim fl As String = ""
            For i As Integer = 1 To lines.Length - 1
                fl = lines(i)
                If fl.Length < 2 Then
                    Throw New FtpFeatureException("not a properly formatted feature line")
                End If
                If fl.Substring(0, 1) <> " " Then
                    Exit For
                End If
                Dim v As String() = SplitFeature(fl)
                _list.Add(New FtpFeature(v(0), v(1)))
            Next

            If fl.Length < FEAT_EPILOGUE_LEN Then
                Throw New FtpFeatureException("epilogue length invalid")
            End If

            Dim epilogue As String = fl.Substring(0, FEAT_EPILOGUE_LEN).ToUpper()
            If epilogue.StartsWith(NO_FEAT_PREAMBLE) Then
                Return
            Else
                Throw New FtpFeatureException("epilogue not found while parsing feature list")
            End If
        End Sub

        Private Function SplitCrLf(list As String) As String()
            Return list.Split(New Char() {ControlChars.Cr, ControlChars.Lf}, StringSplitOptions.RemoveEmptyEntries)
        End Function

        ''' <summary>
        ''' Split the feature line by extracting the feature command from the optional arguments.
        ''' </summary>
        ''' <param name="fl">Feature line to parse.</param>
        ''' <return>Array of two string elements.  The first element contains the command and the second the optional arguments.</return>
        Private Function SplitFeature(fl As String) As String()
            fl = fl.Trim()
            Dim v As String() = fl.Split(New Char() {" "c}, StringSplitOptions.RemoveEmptyEntries)
            If v Is Nothing OrElse v.Length = 0 Then
                Throw New FtpFeatureException("error splitting features")
            End If

            Dim a As String() = New String(1) {}
            a(0) = v(0)
            If v.Length > 1 Then
                a(1) = fl.Substring(v(0).Length)
            Else
                a(1) = ""
            End If

            ' raise the command value to uppercase and trim
            a(0).ToUpper().Trim()
            ' trim the arguments value but leave in native case
            a(1).Trim()

            Return a
        End Function


        Public Function GetEnumerator1() As IEnumerator Implements IEnumerable.GetEnumerator
            Return _list.GetEnumerator()
        End Function

    End Class
End Namespace
